Completed
Push — master ( f03e2f...674b8d )
by Akeda
02:57
created

wcStripePaymentRequest.processPayment   B

Complexity

Conditions 4
Paths 3

Size

Total Lines 32

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
dl 0
loc 32
rs 8.5806
c 1
b 0
f 0
cc 4
nc 3
nop 2
1
/*global jQuery, wcStripePaymentRequestParams, PaymentRequest, Stripe, Promise */
2
/*jshint es3: false */
3
/*jshint devel: true */
4
(function( $ ) {
5
6
	/**
7
	 * WooCommerce Stripe PaymentRequest class.
8
	 *
9
	 * @type {Object}
10
	 */
11
	var wcStripePaymentRequest = {
12
13
		/**
14
		 * Initialize class events.
15
		 */
16
		init: function() {
17
			var self = this;
18
19
			if ( self.hasPaymentRequestSupport() ) {
20
				$( document.body )
21
					.on( 'click', '.cart_totals a.checkout-button', self.initPaymentRequest );
22
			}
23
		},
24
25
		/**
26
		 * Check if browser support PaymentRequest class and if is under HTTPS.
27
		 *
28
		 * @return {Bool}
29
		 */
30
		hasPaymentRequestSupport: function() {
31
			return 'PaymentRequest' in window && 'https:' === window.location.protocol;
32
		},
33
34
		/**
35
		 * Get Stripe supported methods.
36
		 *
37
		 * @return {Array}
38
		 */
39
		getSupportedMethods: function() {
40
			return [
41
				'amex',
42
				'diners',
43
				'discover',
44
				'jcb',
45
				'mastercard',
46
				'visa'
47
			];
48
		},
49
50
		/**
51
		 * Get WC AJAX endpoint URL.
52
		 *
53
		 * @param  {String} endpoint Endpoint.
54
		 * @return {String}
55
		 */
56
		getAjaxURL: function( endpoint ) {
57
			return wcStripePaymentRequestParams.ajax_url
58
				.toString()
59
				.replace( '%%endpoint%%', 'wc_stripe_' + endpoint );
60
		},
61
62
		/**
63
		 * Initialize the PaymentRequest.
64
		 *
65
		 * @param {Object} evt DOM events.
66
		 */
67
		initPaymentRequest: function( evt ) {
68
			evt.preventDefault();
69
			var self = wcStripePaymentRequest;
70
			var data = {
71
				security: wcStripePaymentRequestParams.nonce.payment
72
			};
73
74
			$.ajax({
75
				type:    'POST',
76
				data:    data,
77
				url:     self.getAjaxURL( 'get_cart_details' ),
78
				success: function( response ) {
79
					self.openPaymentRequest( response );
80
				}
81
			});
82
		},
83
84
		/**
85
		 * Open Payment Request modal.
86
		 *
87
		 * @param {Object} details Payment request details.
88
		 */
89
		openPaymentRequest: function( details ) {
90
			var self = this;
91
92
			// PaymentRequest options.
93
			var supportedInstruments = [{
94
				supportedMethods: self.getSupportedMethods()
95
			}];
96
			var options = {
97
				requestPayerPhone: true,
98
				requestPayerEmail: true
99
			};
100
			if ( details.shipping_required ) {
101
				options.requestShipping = true;
102
			}
103
			var paymentDetails = details.order_data;
104
105
			// Init PaymentRequest.
106
			var request = new PaymentRequest( supportedInstruments, paymentDetails, options );
107
108
			// Set up shipping.
109
			request.addEventListener( 'shippingaddresschange', function( evt ) {
110
				evt.updateWith( new Promise( function( resolve ) {
111
					self.updateShippingOptions( paymentDetails, request.shippingAddress, resolve );
112
				}));
113
			});
114
			request.addEventListener( 'shippingoptionchange', function( evt ) {
115
				evt.updateWith( new Promise( function( resolve, reject ) {
116
					self.updateShippingDetails( paymentDetails, request.shippingOption, resolve, reject );
117
				}));
118
			});
119
120
			// Open RequestPayment.
121
			request.show().then( function( payment ) {
122
				self.processPayment( payment );
123
			})
124
			.catch( function( err ) {
125
				console.error( err );
126
			});
127
		},
128
129
		/**
130
		 * Update shipping options.
131
		 *
132
		 * @param {Object}         details Payment details.
133
		 * @param {PaymentAddress} address Shipping address.
134
		 * @param {Function}       resolve The callback to invoke with updated line items and shipping options.
135
		 */
136
		updateShippingOptions: function( details, address, resolve ) {
137
			var self = this;
138
			var data = {
139
				security:  wcStripePaymentRequestParams.nonce.shipping,
140
				country:   address.country,
141
				state:     address.region,
142
				postcode:  address.postalCode,
143
				city:      address.city,
144
				address:   typeof address.addressLine[0] === 'undefined' ? '' : address.addressLine[0],
145
				address_2: typeof address.addressLine[1] === 'undefined' ? '' : address.addressLine[1]
146
			};
147
148
			$.ajax({
149
				type:    'POST',
150
				data:    data,
151
				url:     self.getAjaxURL( 'get_shipping_options' ),
152
				success: function( response ) {
153
					details.shippingOptions = response;
154
					resolve( details );
155
				}
156
			});
157
		},
158
159
		/**
160
		 * Updates the shipping price and the total based on the shipping option.
161
		 *
162
		 * @param {Object}   details        The line items and shipping options.
163
		 * @param {String}   shippingOption User's preferred shipping option to use for shipping price calculations.
164
		 * @param {Function} resolve        The callback to invoke with updated line items and shipping options.
165
		 * @param {Function} reject         The callback to invoke in case of failure.
166
		 */
167
		updateShippingDetails: function( details, shippingOption, resolve, reject ) {
168
			var self     = this;
169
			var selected = null;
170
			var data     = {
171
				security:  wcStripePaymentRequestParams.nonce.update_shipping,
172
				shipping_method: [
173
					shippingOption
174
				]
175
			};
176
177
			$.ajax({
178
				type:    'POST',
179
				data:    data,
180
				url:     self.getAjaxURL( 'update_shipping_method' ),
181
				success: function( response ) {
182
					details.shippingOptions.forEach( function( value, index ) {
183
						if ( value.id === shippingOption ) {
184
							selected = index;
185
							value.selected = true;
186
							details.total.amount.value = parseFloat( response.total );
187
188
							if ( response.items ) {
189
								details.displayItems = response.items;
190
							}
191
						} else {
192
							value.selected = false;
193
						}
194
					});
195
196
					if ( null === selected ) {
197
						reject( wcStripePaymentRequestParams.i18n.unknown_shipping.toString().replace( '[option]', shippingOption ) );
198
					}
199
200
					resolve( details );
201
				}
202
			});
203
		},
204
205
		/**
206
		 * Get order data.
207
		 *
208
		 * @param {PaymentResponse} payment Payment Response instance.
209
		 *
210
		 * @return {Object}
211
		 */
212
		getOrderData: function( payment ) {
213
			var billing  = payment.details.billingAddress;
214
			var shipping = payment.shippingAddress;
215
			var data     = {
216
				_wpnonce:                  wcStripePaymentRequestParams.nonce.checkout,
217
				billing_first_name:        billing.recipient.split( ' ' ).slice( 0, 1 ).join( ' ' ),
218
				billing_last_name:         billing.recipient.split( ' ' ).slice( 1 ).join( ' ' ),
219
				billing_company:           billing.organization,
220
				billing_email:             payment.payerEmail,
221
				billing_phone:             payment.payerPhone,
222
				billing_country:           billing.country,
223
				billing_address_1:         typeof billing.addressLine[0] === 'undefined' ? '' : billing.addressLine[0],
224
				billing_address_2:         typeof billing.addressLine[1] === 'undefined' ? '' : billing.addressLine[1],
225
				billing_city:              billing.city,
226
				billing_state:             billing.region,
227
				billing_postcode:          billing.postalCode,
228
				shipping_first_name:       '',
229
				shipping_last_name:        '',
230
				shipping_company:          '',
231
				shipping_country:          '',
232
				shipping_address_1:        '',
233
				shipping_address_2:        '',
234
				shipping_city:             '',
235
				shipping_state:            '',
236
				shipping_postcode:         '',
237
				shipping_method:           [ payment.shippingOption ],
238
				order_comments:            '',
239
				payment_method:            'stripe',
240
				// 'wc-stripe-payment-token': 'new',
241
				stripe_token:              '',
242
			};
243
244
			if ( shipping ) {
245
				data.shipping_first_name = shipping.recipient.split( ' ' ).slice( 0, 1 ).join( ' ' );
246
				data.shipping_last_name  = shipping.recipient.split( ' ' ).slice( 1 ).join( ' ' );
247
				data.shipping_company    = shipping.organization;
248
				data.shipping_country    = shipping.country;
249
				data.shipping_address_1  = typeof shipping.addressLine[0] === 'undefined' ? '' : shipping.addressLine[0];
250
				data.shipping_address_2  = typeof shipping.addressLine[1] === 'undefined' ? '' : shipping.addressLine[1];
251
				data.shipping_city       = shipping.city;
252
				data.shipping_state      = shipping.region;
253
				data.shipping_postcode   = shipping.postalCode;
254
			}
255
256
			return data;
257
		},
258
259
		/**
260
		 * Get credit card data.
261
		 *
262
		 * @param {PaymentResponse} payment Payment Response instance.
263
		 *
264
		 * @return {Object}
265
		 */
266
		getCardData: function( payment ) {
267
			var billing = payment.details.billingAddress;
268
			var data    = {
269
				number:          payment.details.cardNumber,
270
				cvc:             payment.details.cardSecurityCode,
271
				exp_month:       parseInt( payment.details.expiryMonth, 10 ) || 0,
272
				exp_year:        parseInt( payment.details.expiryYear, 10 ) || 0,
273
				name:            billing.recipient,
274
				address_line1:   typeof billing.addressLine[0] === 'undefined' ? '' : billing.addressLine[0],
275
				address_line2:   typeof billing.addressLine[1] === 'undefined' ? '' : billing.addressLine[1],
276
				address_state:   billing.region,
277
				address_city:    billing.city,
278
				address_zip:     billing.postalCode,
279
				address_country: billing.country
280
			};
281
282
			return data;
283
		},
284
285
		/**
286
		 * Generate error message HTML.
287
		 *
288
		 * @param  {String} message Error message.
289
		 * @return {Object}
290
		 */
291
		getErrorMessageHTML: function( message ) {
292
			return $( '<div class="woocommerce-error" />' ).text( message );
293
		},
294
295
		/**
296
		 * Abort payment and display error messages.
297
		 *
298
		 * @param {PaymentResponse} payment Payment response instance.
299
		 * @param {String}          message Error message to display.
300
		 */
301
		abortPayment: function( payment, message ) {
302
			payment.complete( '' ).then( function() {
303
				var $form = $( '.shop_table.cart' ).closest( 'form' );
304
				$( '.woocommerce-error' ).remove();
305
				$form.before( message );
306
				$( 'html, body' ).animate({
307
					scrollTop: $form.prev( '.woocommerce-error' ).offset().top
308
				}, 600 );
309
			})
310
			.catch( function( err ) {
311
				console.error( err );
312
			});
313
		},
314
315
		/**
316
		 * Complete payment.
317
		 *
318
		 * @param {PaymentResponse} payment Payment response instance.
319
		 * @param {String}          url     Order thank you page URL.
320
		 */
321
		completePayment: function( payment, url ) {
322
			payment.complete( 'success' ).then( function() {
323
				// Success, then redirect to the Thank You page.
324
				window.location = url;
325
			})
326
			.catch( function( err ) {
327
				console.error( err );
328
			});
329
		},
330
331
		/**
332
		 * Process payment.
333
		 *
334
		 * @param {PaymentResponse} payment Payment response instance.
335
		 */
336
		processPayment: function( payment ) {
337
			var self      = this;
338
			var orderData = self.getOrderData( payment );
339
			var cardData  = self.getCardData( payment );
340
341
			Stripe.setPublishableKey( wcStripePaymentRequestParams.stripe.key );
342
			Stripe.createToken( cardData, function( status, response ) {
343
				if ( response.error ) {
344
					self.abortPayment( payment, self.getErrorMessageHTML( response.error.message ) );
345
				} else {
346
					// Check if we allow prepaid cards.
347
					if ( 'no' === wcStripePaymentRequestParams.stripe.allow_prepaid_card && 'prepaid' === response.card.funding ) {
348
						self.abortPayment( payment, self.getErrorMessageHTML( wcStripePaymentRequestParams.i18n.no_prepaid_card ) );
349
					} else {
350
						// Token contains id, last4, and card type.
351
						orderData.stripe_token = response.id;
352
353
						$.ajax({
354
							type:     'POST',
355
							data:     orderData,
356
							dataType: 'json',
357
							url:      self.getAjaxURL( 'create_order' ),
358
							success: function( response ) {
359
								if ( 'success' === response.result ) {
360
									self.completePayment( payment, response.redirect );
361
								} else {
362
									self.abortPayment( payment, response.messages );
363
								}
364
							},
365
							complete: function( jqXHR, textStatus ) {
366
								if ( 'success' !== textStatus ) {
367
									console.error( jqXHR );
368
								}
369
							}
370
						});
371
					}
372
				}
373
			});
374
		}
375
	};
376
377
	wcStripePaymentRequest.init();
378
379
})( jQuery );
380